package edu.xidian.onlinedocument.utils.ot;

import edu.xidian.onlinedocument.entity.OperationData;
import edu.xidian.onlinedocument.handler.excpetions.custom.CustomSimpleException;
import edu.xidian.onlinedocument.service.OperationService;
import edu.xidian.onlinedocument.utils.SpringUtil;
import edu.xidian.onlinedocument.utils.ot.internal.ChainReaction;
import edu.xidian.onlinedocument.utils.ot.internal.Text;
import edu.xidian.onlinedocument.utils.ot.internal.Transform;
import edu.xidian.onlinedocument.utils.ot.internal.operations.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Comparator;
import java.util.Map;
import java.util.Queue;
import java.util.List;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.stream.Collectors;

/**
 * 对Text的管理类，主要负责将服务器内存存储的文档操作和数据库进行同步
 */
@Slf4j
public class TextManagement {

    // private final Logger log = LoggerFactory.getLogger(TextManagement.class);

    /**
     * 该TextManagement对应的文档的id
     */
    private final int docId;
    /**
     * 最新发送出去的版本
     */
    private int lastSendRevision;
    /**
     * 最新保存到数据库的版本
     */
    private int lastSaveRevision;
    /**
     * 文档内容
     */
    private final Text text;
    // 优先级阻塞队列，优点是线程安全且可以通过构造参数Comparator根据版本号确定优先级
    // 也可以用new Comparator<ChangesList>() {} 来创建
    private final Queue<ChangesList> waitForResolveList = new PriorityBlockingQueue<>(20,
            Comparator.comparingInt(ChangesList::getRevision));

    private final OperationService operationService;

    public TextManagement(int docId) {
        this(docId, new Text());
    }

    public TextManagement(int docId, Text text) {
        this.docId = docId;
        this.text = text;
        this.lastSendRevision = 0;
        this.lastSaveRevision = 0;
        this.operationService = SpringUtil.getBean("operationService");
    }

    /**
     * WebSocket中收到操作时调用的方法
     * 核心思想是维护一个缓存队列，存储未处理冲突的操作
     * 当收到一个操作时，将其放入缓存队列
     * 逐个将队列中的操作取出，进行冲突处理，然后得到处理后的新操作，将其应用到文档中
     * @param changesList 从客户端处接收到的操作
     */
    public synchronized boolean receive(ChangesList changesList) throws Exception {
        log.info("text receive - docId=[{}], receive=[{}] ", docId, changesList);
        // 客户端发送的操作版本比服务器操作版本大1以上时，默认为错误数据，不操作
        if (equalTextRevision(changesList.getRevision()) > 1) {
            throw new CustomSimpleException("client's revision is " + equalTextRevision(changesList.getRevision()) + " bigger than server's revision");
            // log.warn("client's revision is [{}] , bigger than server's revision [{}] ", changesList.getRevision(), text.revision());
            // return false;
        }

        // 如果收到空操作，则报错
        if (changesList == null) {
            throw new CustomSimpleException("client's changes list is null");
        }

        // 加入等待队列中
        // .add(changesList);
        // 每加入一个操作，都对等待队列中所有操作进行一次冲突处理，主要目的是处理中间版本的到达问题
        // while(waitForResolveList.isEmpty()) {
            // ChangesList B = waitForResolveList.poll();
            ChangesList B = changesList;
            ChangesList resolve = this.resolveConflicts(B);
            if (resolve == null) {
                log.warn("resolve: [null] ");
            }
            else {
                log.info("resolve: [{}] ", resolve);
                text.apply(resolve);
                this.write();
            }
            /*
            // 如果resolve的size为1，表示B的版本大于或者等于本地版本
            else if (resolve.size() == 1) {
                /*
                // 如果B的版本比本地版本大1以上，则放回等待队列中，直到中间版本的到达
                if(B.getRevision() - text.revision() > 1) {
                    waitForResolveList.add(B);
                    return;
                }

                // 如果B的版本比本地版本大1或者相同，则向本地文档加入操作B或B'
                text.apply(resolve.get(0));
            }
            // 如果resolve的size大于1，表示B的版本小于本地版本
            else if (resolve.size() > 1) {
                System.out.println("需要做链式反应");
                text.apply(resolve.get(0));
                // text.alterSeriesChangesList(B.getRevision() + 1, text.revision(), resolve);
            }
             */
        // }

        /*
        // 如果等待队列中只有该加入的操作队列
        if (waitForTransformList.size() == 1) {
            // 其版本比服务器文档版本大1，则加入文档
            if(equalRevision(changesList.getRevision(), text.revision()) == 1) {
                // 注意text每次调用apply都会使text的revision加1
                text.apply(changesList);
            }
        }

        if (waitForTransformList.size() >= 2) {
            ChangesList A = waitForTransformList.poll();
            ChangesList B = waitForTransformList.poll();
            // 处理冲突
            ChangesList aB = resolveConflicts(A, B);
            if(aB == null) {
                throw new IllegalArgumentException("aB is null");
            }
            text.apply(aB);
            System.out.println(text.toString());
            ChangesList compose = Compose.compose(A, B);
            compose.setRevision(B.getRevision());
            waitForTransformList.add(compose);
        }
         */
        return true;
    }

    /**
     * 返回要发送的操作
     * @return 要发送的操作
     */
    public synchronized List<ChangesList> send() {
        log.info("text send - docId=[{}], last send revision=[{}] , local revision=[{}] ", docId, lastSendRevision, text.getRevision());
        List<ChangesList> list = text.getBetweenChangesList(lastSendRevision + 1, text.getRevision());
        return list;
    }

    /**
     * 关闭文档，当没有用户在查看或编辑该文档时即可关闭
     */
    public synchronized void close() {
        this.write();
        log.info("text close - docId=[{}], ", docId);
    }

    /**
     * 从数据库中读取操作
     * @return 成功/失败: true/false
     */
    public synchronized boolean read() {
        List<OperationData> data = operationService.getOperationDataByDocId(docId);
        if (data == null) {
            return false;
        }
        if(data.size() == 0) {
            return true;
        }
        int revision = data.get(data.size() - 1).getOpRevision();

        // 将从数据库中取得的操作以版本号进行分组
        Map<Integer, List<OperationData>> group = groupByRevision(data);
        try {
            for (Map.Entry<Integer, List<OperationData>> entry:
                    group.entrySet()) {
                ChangesList changesList = new ChangesList();
                changesList.fromOperationData(entry.getValue());
                text.apply(changesList);
            }
        } catch (Exception e) {
            log.warn("text read failed - message=[{}]", e.getMessage(), e);
            throw e;
        }

        this.setLastSaveRevision(revision);
        this.setLastSendRevision(revision);
        log.info("text read - docId=[{}], local revision=[{}], last save revision=[{}], last send revision=[{}]", docId, text.getRevision(), lastSaveRevision, lastSendRevision);
        return true;
    }

    /**
     * 将本地操作序列保存至数据库
     * @return 成功/失败: true/false
     */
    public synchronized boolean write() {
        log.info("text write - lastSaveRevision=[{}] , revision=[{}] ", lastSaveRevision, text.getRevision());
        if (equalTextRevision(lastSaveRevision) == 0) {
            return true;
        }
        List<OperationData> operationData = null;
        ChangesList changesList = null;
        for (int i = lastSaveRevision + 1; i <= text.getRevision(); i++) {
            changesList = text.getChangesList(i);
            operationData = changesList.toOperationData(docId);
            // 查询数据库中是否有与当前操作revision相同的操作，如果有，则返回false
            if (operationService.existsOperationData(docId, i)) {
                // operationService.deleteOperationDataByDocIdAndRevision(docId, i);
                return false;
            }
            operationService.saveSerialOperations(operationData);
        }
        // log.info("operationData: {} ", operationData);
        this.setLastSaveRevision(text.getRevision());
        return true;
    }

    private int equalRevision(int revisionA, int revisionB) {
        if (revisionA > revisionB) {
            return 1;
        }
        else if (revisionA < revisionB) {
            return -1;
        }
        return 0;
    }

    private int equalTextRevision(int revision) {
        return revision - text.getRevision();
    }

    /**
     * 处理两个操作间冲突的方法
     * 分为三种情况：
     * 一，B和服务器文档的版本相同，则做transform，返回B'
     * 二，B比服务器文档的版本大，则返回B
     * 三，B比服务器文档的版本小，则做链式反应，返回链式反应后得到的操作序列
     * @param B 操作B
     * @return ChangesList
     */
    private ChangesList resolveConflicts(ChangesList B) {
        ChangesList res  = null;
        int newRevision = B.getRevision();
        int localRevision = text.getRevision();
        // 基于同一版本处理冲突
        // 只需要关心transform问题
        if (equalRevision(newRevision, localRevision) == 0) {
            // 获取同版本的操作
            ChangesList localChangesList = text.getChangesList(localRevision);
            // 对同版本的操作做transform
            Transform.Result result = Transform.transform(localChangesList, B);
            // 返回B'
            // 记得要设置结果的版本，因为transform算法本身是不关心版本问题的，所以结果也没有版本
            res = result.getRight();
            res.setRevision(localRevision + 1);
            log.info("发生单次OT转换，ChangesA为 [{}]， ChangesB为 [{}] ，得到的B'为 [{}]", localChangesList, B, res);
        }
        // B操作的版本比本地文档的版本大1
        else if (equalRevision(newRevision, localRevision) == 1) {
            log.info("未发生冲突，接受的Changes版本为 [{}]， 本地版本为 [{}]", newRevision, localRevision);
            // 返回B
            res = B;
        }
        // B操作的版本比本地文档的版本小
        else if (equalRevision(newRevision, localRevision) < 0) {
            // 链式反应
            ChangesList chain = ChainReaction.chainReaction(B, text.getBetweenChangesList(newRevision, localRevision));
            log.info("发生链式反应，接受的Changes版本为 [{}]， 本地版本为 [{}], 链式得到的Changes版本为 [{}] ", newRevision, localRevision, chain.getRevision());
            res = chain;
        }
        return res;
    }

    public Text getText() {
        return text;
    }

    /*
    /**
     * 处理两个操作间冲突的方法
     * 分为三种情况：
     * 一，A和B之间版本相同，则做transform，返回B'
     * 二，A和B之间版本相差1，则返回版本大的那一方
     * @param A 操作A
     * @param B 操作B
     * @return ChangesList

    private ChangesList resolveConflicts(ChangesList A, ChangesList B) {
        // 基于同一版本处理冲突
        // 只需要关心transform问题
        if (equalRevision(A.getRevision(), B.getRevision()) == 0) {
            Transform.Result result = Transform.transform(A, B);
            return result.getRight();
        }
        // A操作的版本比B操作的版本小1
        else if (equalRevision(A.getRevision(), B.getRevision()) == -1) {
            return B;
        }
        // A操作的版本比B操作的版本大1
        else if (equalRevision(A.getRevision(), B.getRevision()) == 1) {
            return A;
        }
        return null;
    }
    */

    public void setLastSendRevision(int revision) {
        this.lastSendRevision = revision;
    }

    public void setLastSaveRevision(int revision) {
        this.lastSaveRevision = revision;
    }

    /** list分组，以版本号进行分组
     * @param operationData OperationData对象
     * @return 分组的操作序列
     */
    private Map<Integer, List<OperationData>> groupByRevision(List<OperationData> operationData) {
        Map<Integer, List<OperationData>> map = operationData.stream().collect(Collectors.groupingBy(OperationData::getOpRevision));
        return map;
    }
}
